Skip to content

Conversation

ptrus
Copy link
Contributor

@ptrus ptrus commented Sep 18, 2023

A set of changes that were useful for us to implement a precompile that returns gas usage.

Changes:

  • adds fn used_gas(&self) -> u64; to PrecompileHandle to support querying used_gas in precompiles
  • propagates the "parent" used_gas into the precompiles (since precompile uses its own gasometer that starts at 0)
  • minor typo fix Retreive -> Retrieve

Hope that none of these are too controversial.

@boundless-forest
Copy link
Contributor

propagates the "parent" used_gas into the precompiles (since precompile uses its own gasometer that starts at 0)

I suspect this point. The precompile record_cost implementation like this: https://github.com/rust-blockchain/evm/blob/master/src/executor/stack/executor.rs#L1455-L1462, it uses the parent gasometer directly instead of creating a new one.

@ptrus
Copy link
Contributor Author

ptrus commented Sep 21, 2023

Before executing the precompile a new StackSubstateMetadata with an empty gasometer is created here: https://github.com/rust-blockchain/evm/blob/7ac239a67b8e5075199052aef60fa025a389a612/src/executor/stack/executor.rs#L886

@boundless-forest
Copy link
Contributor

Before executing the precompile a new StackSubstateMetadata with an empty gasometer is created here:

If I remember correctly, the executor will make all tx gas limit as used_gas at first, then reduce this value when executing the opcode or precompile one by one. The gasometer will swallow_commit to edit the accumulated used gas when exiting the executor, search self.gasometer.record_stipend(other.gasometer.gas())?; in the code base. @sorpaas Please help confirm this.

@ptrus
Copy link
Contributor Author

ptrus commented Oct 4, 2023

If I remember correctly, the executor will make all tx gas limit as used_gas at first, then reduce this value when executing the opcode or precompile one by one. The gasometer will swallow_commit to edit the accumulated used gas when exiting the executor, search self.gasometer.record_stipend(other.gasometer.gas())?; in the code base. @sorpaas Please help confirm this.

I believe this is all true as stated. However, this seems a bit tangential to the changes introduced in the PR and doesn’t alter the established behavior.

Breaking down the PR:

  • It allows querying 'used_gas' from within the precompiles. (This commit)
  • Since the precompile uses its own gasometer, we propagate the parent's used gas into the precompile to accurately respond to the 'used_gas' query. We capture the parent's used gas before the mentioned: "executor will make all tx gas limit as used_gas" step is executed. (This commit)

@sorpaas
Copy link
Member

sorpaas commented Nov 23, 2023

It allows querying 'used_gas' from within the precompiles. (This commit)

Would you mind to explain more this use case? This seems to be about querying the internal state of the actual running machine, so I'm wondering if we should just make a new opcode instead.

Precompiles have rather defined semantics -- they are essentially external code, but following all other rules of a normal call stack. For example, they are supposed to also work with CALLCODE/DELEGATECALL. They are supposed to properly OOG like a normal call stack. We can use it for pure functions. We can use it for dynamic functions (with the caveats that you might want to disallow the case when code_address does not equal to context.address). But for other use cases we might just well create a new opcode.

But...

if you really want to go with this route -- the next branch allows this to work. We generalized the runtime state there. So what you do is that you'll create a new struct implementing AsMut<RuntimeState> (wrapping the existing RuntimeState). Then you just pass the parent_used_gas there, and it'll be accessible in the precompile.

@sorpaas
Copy link
Member

sorpaas commented Aug 18, 2025

Here's how this can be implemented in v1.0 without doing anything hacky:

  1. Create wrapper around evm::standard::State, with an additional field of parent_used_gas. Implement AsRef<State> and AsMut<State> as well as other required traits (delegating them to the inner trait).
pub struct WrappedState<'config> {
  inner: State<'config>,
  parent_used_gas: U256,
}

impl<'config> AsRef<...> for WrappedState<'config> { }
// And a lot of other traits that `State<'config>` implements.
  1. Implement InvokerState for WrappedState, and set parent_used_gas in InvokerState::substate.
impl<'config> InvokerState for WrappedState<'config> {
  fn substate(&mut self, ...) -> Result<Self, ExitError> {
    let inner = self.inner.substate(...)?;
    let parent_used_gas = self.gas();
    Ok(Self { inner, parent_used_gas })
  }
}

The information is then available from PrecompileSet (the first type parameter), which you can use to implement your custom precompile.

  1. Use the type in DispatchEtable.
type DispatchEtable<'config, H> = etable::DispatchEtable<WrappedState<'config>, H, CallCreateTrap>;
  1. Profit!

@sorpaas sorpaas closed this Aug 18, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants